/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.Arrays;

class Obj {
    public double value = 0;
    Obj(double value) {
        this.value = value;
    }
}
class PointerCall {
    public static Obj[] GenerateFakeRandomObject() {
        Obj[] resource = new Obj[15];
        Arrays.fill(resource, new Obj((0)));
        for (int i = 0; i < 15; i++) {
            double random = Math.random() * (10) + 1;;
            resource[i] = new Obj(random);
        }
        return resource;
    }

    public static int global_value = 0;
    public static Integer[] GenerateFakeRandomInteger() {
        Integer[] resource = {12, 43, 56, 76, 89, 54, 45, 32, 35, 47, 46, 44, 21, 37, 84};
        return resource;
    }
    public static Integer[] arr = GenerateFakeRandomInteger();
/***** Without parameters *****/

    /***************** With parameters *****************/
    public static void Foo(int a, int b, int c) {
        arr[global_value] += 1;
    }
    public static void CallFoo(TriConsumer<Integer, Integer, Integer> f, int a, int b, int c) {
        f.apply(a, b, c);
    }
    public static double RunFunctionPtr() {
        int count = 10000000;
        global_value = 0;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
            CallFoo(PointerCall::Foo, 1, 2, i);
        }
        double midTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("Function Call - RunFunctionPtr:\t"+time+"\tms");
        return time;
    }

    /***************************** Default  parameters *****************************/
    public static int DefaultFoo(Integer[] resources, int i, int i3,  int resourcesLength) {
        if ((resources[i % i3 & (resourcesLength - 1)] & 1) == 0) {
            i3 += 1;
        } else {
            i3 += 2;
        }
        return i3;
    }

    public static int CallDefaultFoo(Function<Integer[], Integer, Integer, Integer, Integer> f, Integer[] resources, int i, int i3, int resourcesLength) {
        return f.apply(resources, i, i3, resourcesLength);
    }
    public static double RunDefaultfunctionPtr() {
        int count = 10000000;
        global_value = 0;
        int i3 = 1;
        Integer[] resources = GenerateFakeRandomInteger();
        int resourcesLength = resources.length;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
            i3 = CallDefaultFoo(PointerCall::DefaultFoo, resources, i, i3, resourcesLength);
        }
        double midTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("res: " + i3);
        System.out.println("Function Call - RunDefaultfunctionPtr:\t"+time+"\tms");
        return time;
    }

    /********************* Different  parameters *********************/
    public static void DifferentFoo(int a, String b, boolean c) {
        arr[global_value] += 1;
    }
    public static void CallDifferentFoo(TriConsumer<Integer, String, Boolean> f, int a, String b, boolean c) {
        f.apply(a, b, c);
    }

    public static double RunDifferentFunctionPtr() {
        int count = 10000000;
        global_value = 0;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
            CallDifferentFoo(PointerCall::DifferentFoo, 1, "1", true);
        }
        double midTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("Function Call - RunDifferentFunctionPtr:\t"+time+"\tms");
        return time;
    }

    /************************* Variable  parameters *************************/
    public static void VariableFoo(int a, String b, boolean c) {
        arr[global_value] += 1;
    }
    public  static  void CallVariableFoo(TriConsumer<Integer, String, Boolean> f, int a, String b, boolean c) {
        f.apply(a, b, c);
    }
    public static double RunVariableFunctionPtr() {
        int count = 10000000;
        global_value = 0;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
            CallVariableFoo(PointerCall::VariableFoo, 1, "1", true);
        }
        double midTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("Function Call - RunVariableFunctionPtr:\t"+time+"\tms");
        return time;
    }
}

class Benchmark {
    public static void main(String[] args) {
        PointerCall.RunFunctionPtr();
        PointerCall.RunDefaultfunctionPtr();
        PointerCall.RunDifferentFunctionPtr();
        PointerCall.RunVariableFunctionPtr();
        // System.out.println("Ts Method Call Is End, global_value value: \t"+global_value);
    }
}


@FunctionalInterface
interface Function<T, U, V, W, R> {
    R apply(T t, U u, V v, W w);
}

@FunctionalInterface
interface TriConsumer<T, U, V> {
    void apply(T t, U u, V v);
}
